package su2010.puz.classifiers;

import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.List;
import java.util.Random;
import java.util.Set;

import su2010.puz.Extractor;
import su2010.puz.FeatureVector;
import weka.classifiers.Classifier;
import weka.classifiers.Evaluation;
import weka.core.Attribute;
import weka.core.FastVector;
import weka.core.Instance;
import weka.core.Instances;
import weka.core.converters.ConverterUtils.DataSource;

public class Classification {

	private Classifier classifier;
	private FastVector fvWekaAttributes;
	private Instances isTrainingSet;
	private Instances isTestingSet;
	private List<Extractor> extractors;
	private List<String> classes;
	
	public Classification(String name,String[] options,List<Extractor> extractors, List<String> classes) {
		try {
			classifier = Classifier.forName(name, options);
			this.extractors = extractors;
			this.classes = classes;
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		initialize();
	}
	
	/**
	 * Konstruktor klasifikacije kojem se predaje već stvoreni klasifikator
	 * (za lakše testiranje) i loada podatke iz datoteke
	 * @param clsInstance klasifikator
	 */
	public Classification(Classifier clsInstance,String arffFile,int percentage) {
		classifier = clsInstance;
		try {
			loadFromFile(arffFile, percentage);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	/**
	 * Konstruktor kojem se predaje vec stvoreni klasifikator, listu extraktora te lista klasa za 
	 * dinamicko stvaranje klasifikatora
	 * @param clsInstance
	 * @param extractors
	 * @param classes
	 */
	public Classification(Classifier clsInstance, List<Extractor> extractors, List<String> classes){
		classifier = clsInstance;
		this.extractors = extractors;
		this.classes = classes;
		initialize();
	}
	
	public void setExtractors(List<Extractor> extractors) {
		this.extractors = extractors;
	}
	
	/**
	 * initalizing attributes 
	 * also creting empty training and testing sets
	 */
	private void initialize(){
		
		FastVector fvClassVal = new FastVector(classes.size());
		fvWekaAttributes = new FastVector();
		
		for(String cls : classes){
			fvClassVal.addElement(cls);
		}
		Attribute classAtribute = new Attribute("class",fvClassVal);
		
		for(Extractor extractor : extractors){
			if(extractor.getNumOutput() > 2)
				for(int i = 0;i<extractor.getNumOutput();i++)
					fvWekaAttributes.addElement(new Attribute(extractor.getName()+Integer.toString(i)));
			else
				fvWekaAttributes.addElement(new Attribute(extractor.getName()));
		}
		
		fvWekaAttributes.addElement(classAtribute);
		isTrainingSet = new Instances("Rel", fvWekaAttributes, 10);
		isTrainingSet.setClassIndex(fvWekaAttributes.size()-1);
		isTestingSet = new Instances("Rel",fvWekaAttributes,10);
		isTestingSet.setClassIndex(fvWekaAttributes.size()-1);
	}
	
	/**
	 * Loading data from sets of feature vectors
	 * Must be used before train and test if data not loaded from file
	 * @param trainSet
	 * @param testSet
	 */
	public void loadSets(Set<FeatureVector> trainSet, Set<FeatureVector> testSet){
		for(FeatureVector fv : trainSet){
			
			Instance instance = new Instance(fvWekaAttributes.size());
			
			for(int i = 0; i< fv.size(); i++) {
				try {
					instance.setValue((Attribute)fvWekaAttributes.elementAt(i), fv.getFeatures().get(i));
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
			instance.setValue((Attribute)fvWekaAttributes.elementAt(fvWekaAttributes.size()-1), fv.getExampleClass());
			isTrainingSet.add(instance);
		}
		for(FeatureVector fv : testSet){
			
			Instance instance = new Instance(fvWekaAttributes.size());
			for(int i = 0; i< fv.size(); i++)
				instance.setValue((Attribute)fvWekaAttributes.elementAt(i), fv.getFeatures().get(i));
			instance.setValue((Attribute)fvWekaAttributes.elementAt(fvWekaAttributes.size()-1), fv.getExampleClass());
			isTestingSet.add(instance);
		}
	}
	
	/**
	 * Train classifier
	 */
	public void train() {
		try {
			classifier.buildClassifier(isTrainingSet);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/**
	 * Test classifier
	 */
	public void test() {
		try {
			Evaluation eTest = new Evaluation(isTrainingSet);
			eTest.evaluateModel(classifier, isTestingSet);
			
			System.out.println(eTest.toSummaryString());
			System.out.println(eTest.toMatrixString());
			System.out.println(eTest.toClassDetailsString());
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * Saving data to arff file
	 * @param path
	 * @throws IOException
	 */
	public void saveToFile(String path) throws IOException{
		BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new BufferedOutputStream(new FileOutputStream(path))));
		writer.write(isTrainingSet.toString());
		String string = isTestingSet.toString();
		String[] split = string.split("@data");
		writer.write(split[1]);
		writer.close();
	}
	
	/**
	 * Loading data from file
	 * @param path
	 * @param percentage
	 * @throws Exception
	 */
	public void loadFromFile(String path,int percentage) throws Exception {
		DataSource source = new DataSource(path);
		Instances data = source.getDataSet(0);
		data.randomize(new Random());
		int size = data.numInstances();
		int border = (size*percentage)/100;
		
		fvWekaAttributes = new FastVector();
		for(int i = 0;i < data.numAttributes();i++)
			fvWekaAttributes.addElement(data.attribute(i));
		
		isTestingSet = new Instances("Rel",fvWekaAttributes,10);
		isTrainingSet = new Instances("Rel",fvWekaAttributes,10);
		isTestingSet.setClassIndex(fvWekaAttributes.size()-1);
		isTrainingSet.setClassIndex(fvWekaAttributes.size()-1);
		for(int i = 0;i<border;i++){
			isTrainingSet.add(data.instance(i));
		}
		for(int i = border; i<size;i++)
			isTestingSet.add(data.instance(i));
	}

}
